package top.superflow.flow.common

import java.lang.reflect.Method

import top.superflow.exception.RequestUnsupportedException
import top.superflow.flow.runtime.inf.{IOSchema, ParamTypeSchema}
import top.superflow.flow.runtime.io.DefaultIOSchema
import top.superflow.json.util.ParamSchemaUtils
import top.superflow.lang.{ArrayUtils, CollectionUtils, StringUtils}
import top.superflow.utils.MetadataUtils

import scala.collection.mutable
import scala.collection.mutable.ListBuffer

trait OriginalProcessProxyMethodTools {

  protected def getTypeSchema(method : Method) : Map[String, ParamTypeSchema] = {
    var typeSchema = mutable.Map[String, ParamTypeSchema]()
    val parameters = method.getParameters
    for(i <- 0 to parameters.size - 1){
      val param= parameters.apply(i)

      typeSchema += (param.getName -> ParamSchemaUtils.toTypeSchema(param.getType))
    }

    typeSchema.toMap
  }

  protected def getMethod : Method = {
    val methodName = getMethodName
    getMethod(getProcessClass, methodName)
  }

  protected def mapToObjectArray(method : Method, params : Map[String,Any]) : Array[Object]  = {
    if(params == null) {
      return null
    }

    val paramsInOrder = ListBuffer[Object]()
    val paramCount = method.getParameterCount
    val expectedParameters = method.getParameters
    for(i <- 0 until paramCount){
      val parameter = expectedParameters.apply(i)
      val passedInParam = params.get(parameter.getName)
      paramsInOrder += passedInParam.get.asInstanceOf[Object]
    }

    paramsInOrder.toArray
  }

  def getInputSchema: IOSchema = {
    val inputSchema = DefaultIOSchema.getInputSchema

    val method = getMethod

    val parameters = method.getParameters

    if(CollectionUtils.isEmpty(parameters)){
      return inputSchema
    }

    val typeSchemas = getTypeSchema(method)

    inputSchema.setSchema(typeSchemas)

    inputSchema.setCount(parameters.size)

    inputSchema
  }

  def getOutputSchema: IOSchema = {
    val outputStream = DefaultIOSchema.getOutputSchema

    val method = getMethod

    val returnSchema = mutable.Map[String, ParamTypeSchema]()
    val returnType = method.getReturnType

    returnSchema += (ParamSchemaUtils.TYPE_RETURN -> ParamSchemaUtils.toTypeSchema(returnType))


    outputStream.setSchema(returnSchema.toMap)

    outputStream.setCount(returnSchema.size)

    outputStream
  }

  protected def getMethod (clz : Class[_], methodName : String): Method = {
    val methods = getMethods(clz, methodName)

    if(methods.size < 1){
      throw new RequestUnsupportedException("There is no method named as [" + methodName + " for the class [" + clz.getName + "]" )
    }

    if(methods.size > 1){
      locateMethodByParams(clz)
    }else{
      methods.apply(0)
    }
  }

  private def getMethods(clz: Class[_], methodName : String): ListBuffer[Method] ={
    val methodList = new ListBuffer[Method]
    for(method <- clz.getDeclaredMethods){
      if(StringUtils.equals(method.getName, methodName)){
        methodList += method
      }
    }

    methodList
  }

  private def locateMethodByParams (clz : Class[_]): Method = {
    val configuredParamTypes = getParamTypes

    val methodName = getMethodName

    if(ArrayUtils.isEmpty(configuredParamTypes)){
      throw new RequestUnsupportedException("There is no parameter configured, however there are more than one method are named as " + methodName + " in the class " + clz + "")
    }

    val configuredParamTypesInClass : Array[Class[_]]  = configuredParamTypes.toList.map(clzStr => {
      Class.forName(clzStr)
    }).toArray

    clz.getDeclaredMethod(methodName, configuredParamTypesInClass : _*)
  }

  protected def validateParameters(method : Method, params : Map[String,Any]) = {
    if(method == null){
      throw new RequestUnsupportedException("The method of the process should not be null")
    }

    val paramCount = method.getParameterCount
    val passedInParamCount = getParamCount(params)
    if(paramCount != passedInParamCount){
      throw new RequestUnsupportedException("Need " + paramCount + " parameters, yet passed in " + passedInParamCount + "!")
    }

    val expectedParameters = method.getParameters
    for(i <- 0 until paramCount){
      val parameter = expectedParameters.apply(i)
      val passedInParam = params.get(parameter.getName)

      if(passedInParam.isEmpty){
        throw new RequestUnsupportedException("The " + i + " passed in parameter is null, but expect type " + parameter.getType.getName + " !")
      }

      val realPassedInParamValue = passedInParam.get
      if(realPassedInParamValue != null && !parameter.getType.isAssignableFrom(realPassedInParamValue.getClass)){
        throw new RequestUnsupportedException("The " + i + " parameter should be type of " + parameter.getType.getName + " but passed in " + passedInParam.getClass.getName + "!")
      }
    }
  }

  private def getParamCount(params : Map[String,Any]) : Int = {
    if(params == null){
      return 0
    }

    params.size
  }

  protected def getParamTypes : Array[String] = {
    val metada = getMetadata

    if(metada.contains(MetadataUtils.META_DATA_NAME_PARAM_TYPES)){
      return metada.get(MetadataUtils.META_DATA_NAME_PARAM_TYPES).get.asInstanceOf[List[String]].toArray[String]
    }

    val methodName = getMethodName

    throw new RequestUnsupportedException("There is no parameter types configured for the class " + getProcessClass + " of method " + methodName + "!")
  }

  protected def getMethodName : String = {
    val metadata = getMetadata

    if(metadata.contains(MetadataUtils.META_DATA_NAME_METHOD)){
      return metadata.get(MetadataUtils.META_DATA_NAME_METHOD).get.toString
    }

    throw new RequestUnsupportedException("There is no method name configured in the meta data of the process configuration!")
  }


  def getMetadata : Map[String, Any]


  protected def getObject : Any

  protected def getProcessClass : Class[_]
}
